home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / hplip / base / utils.py < prev    next >
Encoding:
Python Source  |  2009-04-14  |  46.7 KB  |  1,565 lines

  1. # -*- coding: utf-8 -*-
  2. #
  3. # (c) Copyright 2001-2009 Hewlett-Packard Development Company, L.P.
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
  18. #
  19. # Author: Don Welch
  20. #
  21. # Thanks to Henrique M. Holschuh <hmh@debian.org> for various security patches
  22. #
  23.  
  24. from __future__ import generators
  25.  
  26. # Std Lib
  27. import sys
  28. import os
  29. import fnmatch
  30. import tempfile
  31. import socket
  32. import struct
  33. import select
  34. import time
  35. import fcntl
  36. import errno
  37. import stat
  38. import string
  39. import commands # TODO: Replace with subprocess (commands is deprecated in Python 3.0)
  40. import cStringIO
  41. import re
  42. import xml.parsers.expat as expat
  43. import getpass
  44. import locale
  45.  
  46. try:
  47.     import platform
  48.     platform_avail = True
  49. except ImportError:
  50.     platform_avail = False
  51.  
  52. # Local
  53. from g import *
  54. from codes import *
  55. import pexpect
  56.  
  57.  
  58.  
  59. def lock(f):
  60.     log.debug("Locking: %s" % f.name)
  61.     try:
  62.         fcntl.flock(f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
  63.         return True
  64.     except (IOError, OSError):
  65.         log.debug("Failed to unlock %s." % f.name)
  66.         return False
  67.  
  68.  
  69. def unlock(f):
  70.     if f is not None:
  71.         log.debug("Unlocking: %s" % f.name)
  72.         try:
  73.             fcntl.flock(f.fileno(), fcntl.LOCK_UN)
  74.             os.remove(f.name)
  75.         except (IOError, OSError):
  76.             pass
  77.  
  78.  
  79. def lock_app(application, suppress_error=False):
  80.     dir = prop.user_dir
  81.     if os.geteuid() == 0:
  82.         dir = '/var'
  83.  
  84.     elif not os.path.exists(dir):
  85.         os.makedirs(dir)
  86.  
  87.     lock_file = os.path.join(dir, '.'.join([application, 'lock']))
  88.     try:
  89.         lock_file_f = open(lock_file, "w")
  90.     except IOError:
  91.         if not suppress_error:
  92.             log.error("Unable to open %s lock file." % lock_file)
  93.         return False, None
  94.  
  95.     #log.debug("Locking file: %s" % lock_file)
  96.  
  97.     if not lock(lock_file_f):
  98.         if not suppress_error:
  99.             log.error("Unable to lock %s. Is %s already running?" % (lock_file, application))
  100.         return False, None
  101.  
  102.     return True, lock_file_f
  103.  
  104.  
  105. xml_basename_pat = re.compile(r"""HPLIP-(\d*)_(\d*)_(\d*).xml""", re.IGNORECASE)
  106.  
  107.  
  108. def Translator(frm='', to='', delete='', keep=None):
  109.     allchars = string.maketrans('','')
  110.  
  111.     if len(to) == 1:
  112.         to = to * len(frm)
  113.     trans = string.maketrans(frm, to)
  114.  
  115.     if keep is not None:
  116.         delete = allchars.translate(allchars, keep.translate(allchars, delete))
  117.  
  118.     def callable(s):
  119.         return s.translate(trans, delete)
  120.  
  121.     return callable
  122.  
  123.  
  124. def to_bool_str(s, default='0'):
  125.     """ Convert an arbitrary 0/1/T/F/Y/N string to a normalized string 0/1."""
  126.     if isinstance(s, str) and s:
  127.         if s[0].lower() in ['1', 't', 'y']:
  128.             return u'1'
  129.         elif s[0].lower() in ['0', 'f', 'n']:
  130.             return u'0'
  131.  
  132.     return default
  133.  
  134. def to_bool(s, default=False):
  135.     """ Convert an arbitrary 0/1/T/F/Y/N string to a boolean True/False value."""
  136.     if isinstance(s, str) and s:
  137.         if s[0].lower() in ['1', 't', 'y']:
  138.             return True
  139.         elif s[0].lower() in ['0', 'f', 'n']:
  140.             return False
  141.     elif isinstance(s, bool):
  142.         return s
  143.  
  144.     return default
  145.  
  146.  
  147. def walkFiles(root, recurse=True, abs_paths=False, return_folders=False, pattern='*', path=None):
  148.     if path is None:
  149.         path = root
  150.  
  151.     try:
  152.         names = os.listdir(root)
  153.     except os.error:
  154.         raise StopIteration
  155.  
  156.     pattern = pattern or '*'
  157.     pat_list = pattern.split(';')
  158.  
  159.     for name in names:
  160.         fullname = os.path.normpath(os.path.join(root, name))
  161.  
  162.         for pat in pat_list:
  163.             if fnmatch.fnmatch(name, pat):
  164.                 if return_folders or not os.path.isdir(fullname):
  165.                     if abs_paths:
  166.                         yield fullname
  167.                     else:
  168.                         try:
  169.                             yield os.path.basename(fullname)
  170.                         except ValueError:
  171.                             yield fullname
  172.  
  173.         #if os.path.islink(fullname):
  174.         #    fullname = os.path.realpath(os.readlink(fullname))
  175.  
  176.         if recurse and os.path.isdir(fullname): # or os.path.islink(fullname):
  177.             for f in walkFiles(fullname, recurse, abs_paths, return_folders, pattern, path):
  178.                 yield f
  179.  
  180.  
  181. def is_path_writable(path):
  182.     if os.path.exists(path):
  183.         s = os.stat(path)
  184.         mode = s[stat.ST_MODE] & 0777
  185.  
  186.         if mode & 02:
  187.             return True
  188.         elif s[stat.ST_GID] == os.getgid() and mode & 020:
  189.             return True
  190.         elif s[stat.ST_UID] == os.getuid() and mode & 0200:
  191.             return True
  192.  
  193.     return False
  194.  
  195.  
  196. # Provides the TextFormatter class for formatting text into columns.
  197. # Original Author: Hamish B Lawson, 1999
  198. # Modified by: Don Welch, 2003
  199. class TextFormatter:
  200.  
  201.     LEFT  = 0
  202.     CENTER = 1
  203.     RIGHT  = 2
  204.  
  205.     def __init__(self, colspeclist):
  206.         self.columns = []
  207.         for colspec in colspeclist:
  208.             self.columns.append(Column(**colspec))
  209.  
  210.     def compose(self, textlist, add_newline=False):
  211.         numlines = 0
  212.         textlist = list(textlist)
  213.         if len(textlist) != len(self.columns):
  214.             log.error("Formatter: Number of text items does not match columns")
  215.             return
  216.         for text, column in map(None, textlist, self.columns):
  217.             column.wrap(text)
  218.             numlines = max(numlines, len(column.lines))
  219.         complines = [''] * numlines
  220.         for ln in range(numlines):
  221.             for column in self.columns:
  222.                 complines[ln] = complines[ln] + column.getline(ln)
  223.         if add_newline:
  224.             return '\n'.join(complines) + '\n'
  225.         else:
  226.             return '\n'.join(complines)
  227.  
  228. class Column:
  229.  
  230.     def __init__(self, width=78, alignment=TextFormatter.LEFT, margin=0):
  231.         self.width = width
  232.         self.alignment = alignment
  233.         self.margin = margin
  234.         self.lines = []
  235.  
  236.     def align(self, line):
  237.         if self.alignment == TextFormatter.CENTER:
  238.             return line.center(self.width)
  239.         elif self.alignment == TextFormatter.RIGHT:
  240.             return line.rjust(self.width)
  241.         else:
  242.             return line.ljust(self.width)
  243.  
  244.     def wrap(self, text):
  245.         self.lines = []
  246.         words = []
  247.         for word in text.split():
  248.             if word <= self.width:
  249.                 words.append(word)
  250.             else:
  251.                 for i in range(0, len(word), self.width):
  252.                     words.append(word[i:i+self.width])
  253.         if not len(words): return
  254.         current = words.pop(0)
  255.         for word in words:
  256.             increment = 1 + len(word)
  257.             if len(current) + increment > self.width:
  258.                 self.lines.append(self.align(current))
  259.                 current = word
  260.             else:
  261.                 current = current + ' ' + word
  262.         self.lines.append(self.align(current))
  263.  
  264.     def getline(self, index):
  265.         if index < len(self.lines):
  266.             return ' '*self.margin + self.lines[index]
  267.         else:
  268.             return ' ' * (self.margin + self.width)
  269.  
  270.  
  271.  
  272. class Stack:
  273.     def __init__(self):
  274.         self.stack = []
  275.  
  276.     def pop(self):
  277.         return self.stack.pop()
  278.  
  279.     def push(self, value):
  280.         self.stack.append(value)
  281.  
  282.     def as_list(self):
  283.         return self.stack
  284.  
  285.     def clear(self):
  286.         self.stack = []
  287.  
  288.     def __len__(self):
  289.         return len(self.stack)
  290.  
  291.  
  292.  
  293. class Queue(Stack):
  294.     def __init__(self):
  295.         Stack.__init__(self)
  296.  
  297.     def get(self):
  298.         return self.stack.pop(0)
  299.  
  300.     def put(self, value):
  301.         Stack.push(self, value)
  302.  
  303.  
  304.  
  305.  
  306. # RingBuffer class
  307. # Source: Python Cookbook 1st Ed., sec. 5.18, pg. 201
  308. # Credit: Sebastien Keim
  309. # License: Modified BSD
  310. class RingBuffer:
  311.     def __init__(self, size_max=50):
  312.         self.max = size_max
  313.         self.data = []
  314.  
  315.     def append(self,x):
  316.         """append an element at the end of the buffer"""
  317.         self.data.append(x)
  318.  
  319.         if len(self.data) == self.max:
  320.             self.cur = 0
  321.             self.__class__ = RingBufferFull
  322.  
  323.     def replace(self, x):
  324.         """replace the last element instead off appending"""
  325.         self.data[-1] = x
  326.  
  327.     def get(self):
  328.         """ return a list of elements from the oldest to the newest"""
  329.         return self.data
  330.  
  331.  
  332. class RingBufferFull:
  333.     def __init__(self, n):
  334.         #raise "you should use RingBuffer"
  335.         pass
  336.  
  337.     def append(self, x):
  338.         self.data[self.cur] = x
  339.         self.cur = (self.cur+1) % self.max
  340.  
  341.     def replace(self, x):
  342.         # back up 1 position to previous location
  343.         self.cur = (self.cur-1) % self.max
  344.         self.data[self.cur] = x
  345.         # setup for next item
  346.         self.cur = (self.cur+1) % self.max
  347.  
  348.     def get(self):
  349.         return self.data[self.cur:] + self.data[:self.cur]
  350.  
  351.  
  352.  
  353. def sort_dict_by_value(d):
  354.     """ Returns the keys of dictionary d sorted by their values """
  355.     items=d.items()
  356.     backitems=[[v[1],v[0]] for v in items]
  357.     backitems.sort()
  358.     return [backitems[i][1] for i in range(0, len(backitems))]
  359.  
  360.  
  361. def commafy(val):
  362.     return unicode(locale.format("%d", val, grouping=True))
  363.  
  364.  
  365. def format_bytes(s, show_bytes=False):
  366.     if s < 1024:
  367.         return ''.join([commafy(s), ' B'])
  368.     elif 1024 < s < 1048576:
  369.         if show_bytes:
  370.             return ''.join([unicode(round(s/1024.0, 1)) , u' KB (',  commafy(s), ')'])
  371.         else:
  372.             return ''.join([unicode(round(s/1024.0, 1)) , u' KB'])
  373.     elif 1048576 < s < 1073741824:
  374.         if show_bytes:
  375.             return ''.join([unicode(round(s/1048576.0, 1)), u' MB (',  commafy(s), ')'])
  376.         else:
  377.             return ''.join([unicode(round(s/1048576.0, 1)), u' MB'])
  378.     else:
  379.         if show_bytes:
  380.             return ''.join([unicode(round(s/1073741824.0, 1)), u' GB (',  commafy(s), ')'])
  381.         else:
  382.             return ''.join([unicode(round(s/1073741824.0, 1)), u' GB'])
  383.  
  384.  
  385.  
  386. try:
  387.     make_temp_file = tempfile.mkstemp # 2.3+
  388. except AttributeError:
  389.     def make_temp_file(suffix='', prefix='', dir='', text=False): # pre-2.3
  390.         path = tempfile.mktemp(suffix)
  391.         fd = os.open(path, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700)
  392.         return ( os.fdopen( fd, 'w+b' ), path )
  393.  
  394.  
  395.  
  396.  
  397. def which(command, return_full_path=False):
  398.     path = os.getenv('PATH').split(':')
  399.  
  400.     # Add these paths for Fedora
  401.     path.append('/sbin')
  402.     path.append('/usr/sbin')
  403.     path.append('/usr/local/sbin')
  404.  
  405.     found_path = ''
  406.     for p in path:
  407.         try:
  408.             files = os.listdir(p)
  409.         except OSError:
  410.             continue
  411.         else:
  412.             if command in files:
  413.                 found_path = p
  414.                 break
  415.  
  416.     if return_full_path:
  417.         if found_path:
  418.             return os.path.join(found_path, command)
  419.         else:
  420.             return ''
  421.     else:
  422.         return found_path
  423.  
  424.  
  425. class UserSettings(object): # Note: Deprecated after 2.8.8 (see ui4/ui_utils.py)
  426.     def __init__(self):
  427.         self.load()
  428.  
  429.     def loadDefaults(self):
  430.         # Print
  431.         self.cmd_print = ''
  432.         path = which('hp-print')
  433.  
  434.         if len(path) > 0:
  435.             self.cmd_print = 'hp-print -p%PRINTER%'
  436.         else:
  437.             path = which('kprinter')
  438.             if len(path) > 0:
  439.                 self.cmd_print = 'kprinter -P%PRINTER% --system cups'
  440.             else:
  441.                 path = which('gtklp')
  442.                 if len(path) > 0:
  443.                     self.cmd_print = 'gtklp -P%PRINTER%'
  444.                 else:
  445.                     path = which('xpp')
  446.                     if len(path) > 0:
  447.                         self.cmd_print = 'xpp -P%PRINTER%'
  448.  
  449.         # Scan
  450.         self.cmd_scan = ''
  451.         path = which('xsane')
  452.  
  453.         if len(path) > 0:
  454.             self.cmd_scan = 'xsane -V %SANE_URI%'
  455.         else:
  456.             path = which('kooka')
  457.             if len(path) > 0:
  458.                 self.cmd_scan = 'kooka'
  459.             else:
  460.                 path = which('xscanimage')
  461.                 if len(path) > 0:
  462.                     self.cmd_scan = 'xscanimage'
  463.  
  464.         # Photo Card
  465.         path = which('hp-unload')
  466.  
  467.         if len(path):
  468.             self.cmd_pcard = 'hp-unload -d %DEVICE_URI%'
  469.         else:
  470.             self.cmd_pcard = 'python %HOME%/unload.py -d %DEVICE_URI%'
  471.  
  472.         # Copy
  473.         path = which('hp-makecopies')
  474.  
  475.         if len(path):
  476.             self.cmd_copy = 'hp-makecopies -d %DEVICE_URI%'
  477.         else:
  478.             self.cmd_copy = 'python %HOME%/makecopies.py -d %DEVICE_URI%'
  479.  
  480.         # Fax
  481.         path = which('hp-sendfax')
  482.  
  483.         if len(path):
  484.             self.cmd_fax = 'hp-sendfax -d %FAX_URI%'
  485.         else:
  486.             self.cmd_fax = 'python %HOME%/sendfax.py -d %FAX_URI%'
  487.  
  488.         # Fax Address Book
  489.         path = which('hp-fab')
  490.  
  491.         if len(path):
  492.             self.cmd_fab = 'hp-fab'
  493.         else:
  494.             self.cmd_fab = 'python %HOME%/fab.py'
  495.  
  496.     def load(self):
  497.         self.loadDefaults()
  498.         log.debug("Loading user settings...")
  499.         self.auto_refresh = to_bool(user_conf.get('refresh', 'enable', '0'))
  500.  
  501.         try:
  502.             self.auto_refresh_rate = int(user_conf.get('refresh', 'rate', '30'))
  503.         except ValueError:
  504.             self.auto_refresh_rate = 30 # (secs)
  505.  
  506.         try:
  507.             self.auto_refresh_type = int(user_conf.get('refresh', 'type', '0'))
  508.         except ValueError:
  509.             self.auto_refresh_type = 0 # refresh 1 (1=refresh all)
  510.  
  511.         self.cmd_print = user_conf.get('commands', 'prnt', self.cmd_print)
  512.         self.cmd_scan = user_conf.get('commands', 'scan', self.cmd_scan)
  513.         self.cmd_pcard = user_conf.get('commands', 'pcard', self.cmd_pcard)
  514.         self.cmd_copy = user_conf.get('commands', 'cpy', self.cmd_copy)
  515.         self.cmd_fax = user_conf.get('commands', 'fax', self.cmd_fax)
  516.         self.cmd_fab = user_conf.get('commands', 'fab', self.cmd_fab)
  517.         self.debug()
  518.  
  519.     def debug(self):
  520.         log.debug("Print command: %s" % self.cmd_print)
  521.         log.debug("PCard command: %s" % self.cmd_pcard)
  522.         log.debug("Fax command: %s" % self.cmd_fax)
  523.         log.debug("FAB command: %s" % self.cmd_fab)
  524.         log.debug("Copy command: %s " % self.cmd_copy)
  525.         log.debug("Scan command: %s" % self.cmd_scan)
  526.         log.debug("Auto refresh: %s" % self.auto_refresh)
  527.         log.debug("Auto refresh rate: %s" % self.auto_refresh_rate)
  528.         log.debug("Auto refresh type: %s" % self.auto_refresh_type)
  529.  
  530.     def save(self):
  531.         log.debug("Saving user settings...")
  532.         user_conf.set('commands', 'prnt', self.cmd_print)
  533.         user_conf.set('commands', 'pcard', self.cmd_pcard)
  534.         user_conf.set('commands', 'fax', self.cmd_fax)
  535.         user_conf.set('commands', 'scan', self.cmd_scan)
  536.         user_conf.set('commands', 'cpy', self.cmd_copy)
  537.         user_conf.set('refresh', 'enable',self.auto_refresh)
  538.         user_conf.set('refresh', 'rate', self.auto_refresh_rate)
  539.         user_conf.set('refresh', 'type', self.auto_refresh_type)
  540.         self.debug()
  541.  
  542.  
  543.  
  544. def no_qt_message_gtk():
  545.     try:
  546.         import gtk
  547.         w = gtk.Window()
  548.         dialog = gtk.MessageDialog(w, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
  549.                                    gtk.MESSAGE_WARNING, gtk.BUTTONS_OK,
  550.                                    "PyQt not installed. GUI not available. Please check that the PyQt package is installed. Exiting.")
  551.         dialog.run()
  552.         dialog.destroy()
  553.  
  554.     except ImportError:
  555.         log.error("PyQt not installed. GUI not available. Please check that the PyQt package is installed. Exiting.")
  556.  
  557.  
  558. def canEnterGUIMode(): # qt3
  559.     if not prop.gui_build:
  560.         log.warn("GUI mode disabled in build.")
  561.         return False
  562.  
  563.     elif not os.getenv('DISPLAY'):
  564.         log.warn("No display found.")
  565.         return False
  566.  
  567.     elif not checkPyQtImport():
  568.         log.warn("Qt/PyQt 3 initialization failed.")
  569.         return False
  570.  
  571.     return True
  572.  
  573. def canEnterGUIMode4(): # qt4
  574.     if not prop.gui_build:
  575.         log.warn("GUI mode disabled in build.")
  576.         return False
  577.  
  578.     elif not os.getenv('DISPLAY'):
  579.         log.warn("No display found.")
  580.         return False
  581.  
  582.     elif not checkPyQtImport4():
  583.         log.warn("Qt/PyQt 4 initialization failed.")
  584.         return False
  585.  
  586.     return True
  587.  
  588. def checkPyQtImport(): # qt3
  589.     # PyQt
  590.     try:
  591.         import qt
  592.     except ImportError:
  593.         if os.getenv('DISPLAY') and os.getenv('STARTED_FROM_MENU'):
  594.             no_qt_message_gtk()
  595.  
  596.         log.error("PyQt not installed. GUI not available. Exiting.")
  597.         return False
  598.  
  599.     # check version of Qt
  600.     qtMajor = int(qt.qVersion().split('.')[0])
  601.  
  602.     if qtMajor < MINIMUM_QT_MAJOR_VER:
  603.  
  604.         log.error("Incorrect version of Qt installed. Ver. 3.0.0 or greater required.")
  605.         return False
  606.  
  607.     #check version of PyQt
  608.     try:
  609.         pyqtVersion = qt.PYQT_VERSION_STR
  610.     except AttributeError:
  611.         pyqtVersion = qt.PYQT_VERSION
  612.  
  613.     while pyqtVersion.count('.') < 2:
  614.         pyqtVersion += '.0'
  615.  
  616.     (maj_ver, min_ver, pat_ver) = pyqtVersion.split('.')
  617.  
  618.     if pyqtVersion.find('snapshot') >= 0:
  619.         log.warning("A non-stable snapshot version of PyQt is installed.")
  620.     else:
  621.         try:
  622.             maj_ver = int(maj_ver)
  623.             min_ver = int(min_ver)
  624.             pat_ver = int(pat_ver)
  625.         except ValueError:
  626.             maj_ver, min_ver, pat_ver = 0, 0, 0
  627.  
  628.         if maj_ver < MINIMUM_PYQT_MAJOR_VER or \
  629.             (maj_ver == MINIMUM_PYQT_MAJOR_VER and min_ver < MINIMUM_PYQT_MINOR_VER):
  630.             log.error("This program may not function properly with the version of PyQt that is installed (%d.%d.%d)." % (maj_ver, min_ver, pat_ver))
  631.             log.error("Incorrect version of pyQt installed. Ver. %d.%d or greater required." % (MINIMUM_PYQT_MAJOR_VER, MINIMUM_PYQT_MINOR_VER))
  632.             log.error("This program will continue, but you may experience errors, crashes or other problems.")
  633.             return True
  634.  
  635.     return True
  636.  
  637. def checkPyQtImport4():
  638.     try:
  639.         import PyQt4
  640.     except ImportError:
  641.         return False
  642.     else:
  643.         return True
  644.  
  645.  
  646. try:
  647.     from string import Template # will fail in Python <= 2.3
  648. except ImportError:
  649.     # Code from Python 2.4 string.py
  650.     #import re as _re
  651.  
  652.     class _multimap:
  653.         """Helper class for combining multiple mappings.
  654.  
  655.         Used by .{safe_,}substitute() to combine the mapping and keyword
  656.         arguments.
  657.         """
  658.         def __init__(self, primary, secondary):
  659.             self._primary = primary
  660.             self._secondary = secondary
  661.  
  662.         def __getitem__(self, key):
  663.             try:
  664.                 return self._primary[key]
  665.             except KeyError:
  666.                 return self._secondary[key]
  667.  
  668.  
  669.     class _TemplateMetaclass(type):
  670.         pattern = r"""
  671.         %(delim)s(?:
  672.           (?P<escaped>%(delim)s) |   # Escape sequence of two delimiters
  673.           (?P<named>%(id)s)      |   # delimiter and a Python identifier
  674.           {(?P<braced>%(id)s)}   |   # delimiter and a braced identifier
  675.           (?P<invalid>)              # Other ill-formed delimiter exprs
  676.         )
  677.         """
  678.  
  679.         def __init__(cls, name, bases, dct):
  680.             super(_TemplateMetaclass, cls).__init__(name, bases, dct)
  681.             if 'pattern' in dct:
  682.                 pattern = cls.pattern
  683.             else:
  684.                 pattern = _TemplateMetaclass.pattern % {
  685.                     'delim' : re.escape(cls.delimiter),
  686.                     'id'    : cls.idpattern,
  687.                     }
  688.             cls.pattern = re.compile(pattern, re.IGNORECASE | re.VERBOSE)
  689.  
  690.  
  691.     class Template:
  692.         """A string class for supporting $-substitutions."""
  693.         __metaclass__ = _TemplateMetaclass
  694.  
  695.         delimiter = '$'
  696.         idpattern = r'[_a-z][_a-z0-9]*'
  697.  
  698.         def __init__(self, template):
  699.             self.template = template
  700.  
  701.         # Search for $$, $identifier, ${identifier}, and any bare $'s
  702.         def _invalid(self, mo):
  703.             i = mo.start('invalid')
  704.             lines = self.template[:i].splitlines(True)
  705.             if not lines:
  706.                 colno = 1
  707.                 lineno = 1
  708.             else:
  709.                 colno = i - len(''.join(lines[:-1]))
  710.                 lineno = len(lines)
  711.             raise ValueError('Invalid placeholder in string: line %d, col %d' %
  712.                              (lineno, colno))
  713.  
  714.         def substitute(self, *args, **kws):
  715.             if len(args) > 1:
  716.                 raise TypeError('Too many positional arguments')
  717.             if not args:
  718.                 mapping = kws
  719.             elif kws:
  720.                 mapping = _multimap(kws, args[0])
  721.             else:
  722.                 mapping = args[0]
  723.             # Helper function for .sub()
  724.             def convert(mo):
  725.                 # Check the most common path first.
  726.                 named = mo.group('named') or mo.group('braced')
  727.                 if named is not None:
  728.                     val = mapping[named]
  729.                     # We use this idiom instead of str() because the latter will
  730.                     # fail if val is a Unicode containing non-ASCII characters.
  731.                     return '%s' % val
  732.                 if mo.group('escaped') is not None:
  733.                     return self.delimiter
  734.                 if mo.group('invalid') is not None:
  735.                     self._invalid(mo)
  736.                 raise ValueError('Unrecognized named group in pattern',
  737.                                  self.pattern)
  738.             return self.pattern.sub(convert, self.template)
  739.  
  740.         def safe_substitute(self, *args, **kws):
  741.             if len(args) > 1:
  742.                 raise TypeError('Too many positional arguments')
  743.             if not args:
  744.                 mapping = kws
  745.             elif kws:
  746.                 mapping = _multimap(kws, args[0])
  747.             else:
  748.                 mapping = args[0]
  749.             # Helper function for .sub()
  750.             def convert(mo):
  751.                 named = mo.group('named')
  752.                 if named is not None:
  753.                     try:
  754.                         # We use this idiom instead of str() because the latter
  755.                         # will fail if val is a Unicode containing non-ASCII
  756.                         return '%s' % mapping[named]
  757.                     except KeyError:
  758.                         return self.delimiter + named
  759.                 braced = mo.group('braced')
  760.                 if braced is not None:
  761.                     try:
  762.                         return '%s' % mapping[braced]
  763.                     except KeyError:
  764.                         return self.delimiter + '{' + braced + '}'
  765.                 if mo.group('escaped') is not None:
  766.                     return self.delimiter
  767.                 if mo.group('invalid') is not None:
  768.                     return self.delimiter
  769.                 raise ValueError('Unrecognized named group in pattern',
  770.                                  self.pattern)
  771.             return self.pattern.sub(convert, self.template)
  772.  
  773.  
  774.  
  775. #cat = lambda _ : Template(_).substitute(sys._getframe(1).f_globals, **sys._getframe(1).f_locals)
  776.  
  777. def cat(s):
  778.     globals = sys._getframe(1).f_globals.copy()
  779.     if 'self' in globals:
  780.         del globals['self']
  781.  
  782.     locals = sys._getframe(1).f_locals.copy()
  783.     if 'self' in locals:
  784.         del locals['self']
  785.  
  786.     return Template(s).substitute(sys._getframe(1).f_globals, **locals)
  787.  
  788. identity = string.maketrans('','')
  789. unprintable = identity.translate(identity, string.printable)
  790.  
  791. def printable(s):
  792.     return s.translate(identity, unprintable)
  793.  
  794.  
  795. def any(S,f=lambda x:x):
  796.     for x in S:
  797.         if f(x): return True
  798.     return False
  799.  
  800. def all(S,f=lambda x:x):
  801.     for x in S:
  802.         if not f(x): return False
  803.     return True
  804.  
  805. BROWSERS = ['firefox', 'mozilla', 'konqueror', 'galeon', 'skipstone'] # in preferred order
  806. BROWSER_OPTS = {'firefox': '-new-window', 'mozilla' : '', 'konqueror': '', 'galeon': '-w', 'skipstone': ''}
  807.  
  808. def find_browser():
  809.     if platform_avail and platform.system() == 'Darwin':
  810.         return "open"
  811.     else:
  812.         for b in BROWSERS:
  813.             if which(b):
  814.                 return b
  815.         else:
  816.             return None
  817.  
  818. def openURL(url):
  819.     if platform_avail and platform.system() == 'Darwin':
  820.         cmd = 'open "%s"' % url
  821.         log.debug(cmd)
  822.         os.system(cmd)
  823.     else:
  824.         for b in BROWSERS:
  825.             bb = which(b)
  826.             if bb:
  827.                 bb = os.path.join(bb, b)
  828.                 cmd = """%s %s "%s" &""" % (bb, BROWSER_OPTS[b], url)
  829.                 log.debug(cmd)
  830.                 os.system(cmd)
  831.                 break
  832.         else:
  833.             log.warn("Unable to open URL: %s" % url)
  834.  
  835.  
  836. def uniqueList(input):
  837.     temp = []
  838.     [temp.append(i) for i in input if not temp.count(i)]
  839.     return temp
  840.  
  841.  
  842. def list_move_up(l, m, cmp=None):
  843.     if cmp is None:
  844.         f = lambda x: l[x] == m
  845.     else:
  846.         f = lambda x: cmp(l[x], m)
  847.  
  848.     for i in range(1, len(l)):
  849.         if f(i):
  850.             l[i-1], l[i] = l[i], l[i-1]
  851.  
  852.  
  853. def list_move_down(l, m, cmp=None):
  854.     if cmp is None:
  855.         f = lambda x: l[x] == m
  856.     else:
  857.         f = lambda x: cmp(l[x], m)
  858.  
  859.     for i in range(len(l)-2, -1, -1):
  860.         if f(i):
  861.             l[i], l[i+1] = l[i+1], l[i]
  862.  
  863.  
  864.  
  865. class XMLToDictParser:
  866.     def __init__(self):
  867.         self.stack = []
  868.         self.data = {}
  869.         self.last_start = ''
  870.  
  871.     def startElement(self, name, attrs):
  872.         #print "START:", name, attrs
  873.         self.stack.append(str(name).lower())
  874.         self.last_start = str(name).lower()
  875.  
  876.         if len(attrs):
  877.             for a in attrs:
  878.                 self.stack.append(str(a).lower())
  879.                 self.addData(attrs[a])
  880.                 self.stack.pop()
  881.  
  882.     def endElement(self, name):
  883.         if name.lower() == self.last_start:
  884.             self.addData('')
  885.  
  886.         #print "END:", name
  887.         self.stack.pop()
  888.  
  889.     def charData(self, data):
  890.         data = str(data).strip()
  891.  
  892.         if data and self.stack:
  893.             self.addData(data)
  894.  
  895.     def addData(self, data):
  896.         #print "DATA:", data
  897.         self.last_start = ''
  898.         try:
  899.             data = int(data)
  900.         except ValueError:
  901.             data = str(data)
  902.  
  903.         stack_str = '-'.join(self.stack)
  904.         stack_str_0 = '-'.join([stack_str, '0'])
  905.  
  906.         try:
  907.             self.data[stack_str]
  908.         except KeyError:
  909.             try:
  910.                 self.data[stack_str_0]
  911.             except KeyError:
  912.                 self.data[stack_str] = data
  913.             else:
  914.                 j = 2
  915.                 while True:
  916.                     try:
  917.                         self.data['-'.join([stack_str, str(j)])]
  918.                     except KeyError:
  919.                         self.data['-'.join([stack_str, str(j)])] = data
  920.                         break
  921.                     j += 1
  922.  
  923.         else:
  924.             self.data[stack_str_0] = self.data[stack_str]
  925.             self.data['-'.join([stack_str, '1'])] = data
  926.             del self.data[stack_str]
  927.  
  928.  
  929.     def parseXML(self, text):
  930.         parser = expat.ParserCreate()
  931.         parser.StartElementHandler = self.startElement
  932.         parser.EndElementHandler = self.endElement
  933.         parser.CharacterDataHandler = self.charData
  934.         parser.Parse(text, True)
  935.         return self.data
  936.  
  937.  
  938. def dquote(s):
  939.     return ''.join(['"', s, '"'])
  940.  
  941. # Python 2.2.x compatibility functions (strip() family with char argument added in Python 2.2.3)
  942. if sys.hexversion < 0x020203f0:
  943.     def xlstrip(s, chars=' '):
  944.         i = 0
  945.         for c, i in zip(s, range(len(s))):
  946.             if c not in chars:
  947.                 break
  948.  
  949.         return s[i:]
  950.  
  951.     def xrstrip(s, chars=' '):
  952.         return xreverse(xlstrip(xreverse(s), chars))
  953.  
  954.     def xreverse(s):
  955.         l = list(s)
  956.         l.reverse()
  957.         return ''.join(l)
  958.  
  959.     def xstrip(s, chars=' '):
  960.         return xreverse(xlstrip(xreverse(xlstrip(s, chars)), chars))
  961.  
  962. else:
  963.     xlstrip = string.lstrip
  964.     xrstrip = string.rstrip
  965.     xstrip = string.strip
  966.  
  967.  
  968. def getBitness():
  969.     if platform_avail:
  970.         return int(platform.architecture()[0][:-3])
  971.     else:
  972.         return struct.calcsize("P") << 3
  973.  
  974. def getProcessor():
  975.     if platform_avail:
  976.         return platform.machine().replace(' ', '_').lower() # i386, i686, power_macintosh, etc.
  977.     else:
  978.         return "i686" # TODO: Need a fix here
  979.  
  980.  
  981. BIG_ENDIAN = 0
  982. LITTLE_ENDIAN = 1
  983.  
  984. #def getEndian():
  985.     #if struct.pack("@I", 0x01020304)[0] == '\x01':
  986.         #return BIG_ENDIAN
  987.     #else:
  988.         #return LITTLE_ENDIAN
  989.  
  990. def getEndian():
  991.     if sys.byteorder == 'big':
  992.         return BIG_ENDIAN
  993.     else:
  994.         return LITTLE_ENDIAN
  995.  
  996.  
  997. def get_password():
  998.     return getpass.getpass("Enter password: ")
  999.  
  1000.  
  1001. def run(cmd, log_output=True, password_func=get_password, timeout=1):
  1002.     output = cStringIO.StringIO()
  1003.  
  1004.     try:
  1005.         child = pexpect.spawn(cmd, timeout=timeout)
  1006.     except pexpect.ExceptionPexpect:
  1007.         return -1, ''
  1008.  
  1009.     try:
  1010.         while True:
  1011.             update_spinner()
  1012.             i = child.expect(["[pP]assword:", pexpect.EOF, pexpect.TIMEOUT])
  1013.  
  1014.             if child.before:
  1015.                 output.write(child.before)
  1016.                 if log_output:
  1017.                     log.debug(child.before)
  1018.  
  1019.             if i == 0: # Password:
  1020.                 if password_func is not None:
  1021.                     child.sendline(password_func())
  1022.                 else:
  1023.                     child.sendline(get_password())
  1024.  
  1025.             elif i == 1: # EOF
  1026.                 break
  1027.  
  1028.             elif i == 2: # TIMEOUT
  1029.                 continue
  1030.  
  1031.  
  1032.     except Exception, e:
  1033.         log.error("Exception: %s" % e)
  1034.  
  1035.     cleanup_spinner()
  1036.     child.close()
  1037.  
  1038.     return child.exitstatus, output.getvalue()
  1039.  
  1040.  
  1041. def expand_range(ns): # ns -> string repr. of numeric range, e.g. "1-4, 7, 9-12"
  1042.     """Credit: Jean Brouwers, comp.lang.python 16-7-2004
  1043.        Convert a string representation of a set of ranges into a
  1044.        list of ints, e.g.
  1045.        u"1-4, 7, 9-12" --> [1,2,3,4,7,9,10,11,12]
  1046.     """
  1047.     fs = []
  1048.     for n in ns.split(u','):
  1049.         n = n.strip()
  1050.         r = n.split('-')
  1051.         if len(r) == 2:  # expand name with range
  1052.             h = r[0].rstrip(u'0123456789')  # header
  1053.             r[0] = r[0][len(h):]
  1054.              # range can't be empty
  1055.             if not (r[0] and r[1]):
  1056.                 raise ValueError, 'empty range: ' + n
  1057.              # handle leading zeros
  1058.             if r[0] == u'0' or r[0][0] != u'0':
  1059.                 h += '%d'
  1060.             else:
  1061.                 w = [len(i) for i in r]
  1062.                 if w[1] > w[0]:
  1063.                    raise ValueError, 'wide range: ' + n
  1064.                 h += u'%%0%dd' % max(w)
  1065.              # check range
  1066.             r = [int(i, 10) for i in r]
  1067.             if r[0] > r[1]:
  1068.                raise ValueError, 'bad range: ' + n
  1069.             for i in range(r[0], r[1]+1):
  1070.                 fs.append(h % i)
  1071.         else:  # simple name
  1072.             fs.append(n)
  1073.  
  1074.      # remove duplicates
  1075.     fs = dict([(n, i) for i, n in enumerate(fs)]).keys()
  1076.      # convert to ints and sort
  1077.     fs = [int(x) for x in fs if x]
  1078.     fs.sort()
  1079.  
  1080.     return fs
  1081.  
  1082.  
  1083. def collapse_range(x): # x --> sorted list of ints
  1084.     """ Convert a list of integers into a string
  1085.         range representation:
  1086.         [1,2,3,4,7,9,10,11,12] --> u"1-4,7,9-12"
  1087.     """
  1088.     if not x:
  1089.         return ''
  1090.  
  1091.     s, c, r = [str(x[0])], x[0], False
  1092.  
  1093.     for i in x[1:]:
  1094.         if i == (c+1):
  1095.             r = True
  1096.         else:
  1097.             if r:
  1098.                 s.append(u'-%s,%s' % (c,i))
  1099.                 r = False
  1100.             else:
  1101.                 s.append(u',%s' % i)
  1102.  
  1103.         c = i
  1104.  
  1105.     if r:
  1106.         s.append(u'-%s' % i)
  1107.  
  1108.     return ''.join(s)
  1109.  
  1110.  
  1111. def createSequencedFilename(basename, ext, dir=None, digits=3):
  1112.     if dir is None:
  1113.         dir = os.getcwd()
  1114.  
  1115.     m = 0
  1116.     for f in walkFiles(dir, recurse=False, abs_paths=False, return_folders=False, pattern='*', path=None):
  1117.         r, e = os.path.splitext(f)
  1118.  
  1119.         if r.startswith(basename) and ext == e:
  1120.             try:
  1121.                 i = int(r[len(basename):])
  1122.             except ValueError:
  1123.                 continue
  1124.             else:
  1125.                 m = max(m, i)
  1126.  
  1127.     return os.path.join(dir, "%s%0*d%s" % (basename, digits, m+1, ext))
  1128.  
  1129.  
  1130. def validate_language(lang, default='en_US'):
  1131.     if lang is None:
  1132.         loc, encoder = locale.getdefaultlocale()
  1133.     else:
  1134.         lang = lang.lower().strip()
  1135.         for loc, ll in supported_locales.items():
  1136.             if lang in ll:
  1137.                 break
  1138.         else:
  1139.             loc = 'en_US'
  1140.             log.warn("Unknown lang/locale. Using default of %s." % loc)
  1141.  
  1142.     return loc
  1143.  
  1144.  
  1145. def gen_random_uuid():
  1146.     try:
  1147.         import uuid # requires Python 2.5+
  1148.         return str(uuid.uuid4())
  1149.  
  1150.     except ImportError:
  1151.         uuidgen = which("uuidgen")
  1152.         if uuidgen:
  1153.             uuidgen = os.path.join(uuidgen, "uuidgen")
  1154.             return commands.getoutput(uuidgen) # TODO: Replace with subprocess (commands is deprecated in Python 3.0)
  1155.         else:
  1156.             return ''
  1157.  
  1158.  
  1159. class RestTableFormatter(object):
  1160.     def __init__(self, header=None):
  1161.         self.header = header # tuple of strings
  1162.         self.rows = [] # list of tuples
  1163.  
  1164.     def add(self, row_data): # tuple of strings
  1165.         self.rows.append(row_data)
  1166.  
  1167.     def output(self, w):
  1168.         if self.rows:
  1169.             num_cols = len(self.rows[0])
  1170.             for r in self.rows:
  1171.                 if len(r) != num_cols:
  1172.                     log.error("Invalid number of items in row: %s" % r)
  1173.                     return
  1174.  
  1175.             if len(self.header) != num_cols:
  1176.                 log.error("Invalid number of items in header.")
  1177.  
  1178.             col_widths = []
  1179.             for x, c in enumerate(self.header):
  1180.                 max_width = len(c)
  1181.                 for r in self.rows:
  1182.                     max_width = max(max_width, len(r[x]))
  1183.  
  1184.                 col_widths.append(max_width+2)
  1185.  
  1186.             x = '+'
  1187.             for c in col_widths:
  1188.                 x = ''.join([x, '-' * (c+2), '+'])
  1189.  
  1190.             x = ''.join([x, '\n'])
  1191.             w.write(x)
  1192.  
  1193.             # header
  1194.             if self.header:
  1195.                 x = '|'
  1196.                 for i, c in enumerate(col_widths):
  1197.                     x = ''.join([x, ' ', self.header[i], ' ' * (c+1-len(self.header[i])), '|'])
  1198.  
  1199.                 x = ''.join([x, '\n'])
  1200.                 w.write(x)
  1201.  
  1202.                 x = '+'
  1203.                 for c in col_widths:
  1204.                     x = ''.join([x, '=' * (c+2), '+'])
  1205.  
  1206.                 x = ''.join([x, '\n'])
  1207.                 w.write(x)
  1208.  
  1209.             # data rows
  1210.             for j, r in enumerate(self.rows):
  1211.                 x = '|'
  1212.                 for i, c in enumerate(col_widths):
  1213.                     x = ''.join([x, ' ', self.rows[j][i], ' ' * (c+1-len(self.rows[j][i])), '|'])
  1214.  
  1215.                 x = ''.join([x, '\n'])
  1216.                 w.write(x)
  1217.  
  1218.                 x = '+'
  1219.                 for c in col_widths:
  1220.                     x = ''.join([x, '-' * (c+2), '+'])
  1221.  
  1222.                 x = ''.join([x, '\n'])
  1223.                 w.write(x)
  1224.  
  1225.         else:
  1226.             log.error("No data rows")
  1227.  
  1228.  
  1229. def mixin(cls):
  1230.     import inspect
  1231.  
  1232.     locals = inspect.stack()[1][0].f_locals
  1233.     if "__module__" not in locals:
  1234.         raise TypeError("Must call mixin() from within class def.")
  1235.  
  1236.     dict = cls.__dict__.copy()
  1237.     dict.pop("__doc__", None)
  1238.     dict.pop("__module__", None)
  1239.  
  1240.     locals.update(dict)
  1241.  
  1242.  
  1243.  
  1244. # TODO: Move usage stuff to to base/module/Module class
  1245.  
  1246.  
  1247.  # ------------------------- Usage Help
  1248. USAGE_OPTIONS = ("[OPTIONS]", "", "heading", False)
  1249. USAGE_LOGGING1 = ("Set the logging level:", "-l<level> or --logging=<level>", 'option', False)
  1250. USAGE_LOGGING2 = ("", "<level>: none, info\*, error, warn, debug (\*default)", "option", False)
  1251. USAGE_LOGGING3 = ("Run in debug mode:", "-g (same as option: -ldebug)", "option", False)
  1252. USAGE_LOGGING_PLAIN = ("Output plain text only:", "-t", "option", False)
  1253. USAGE_ARGS = ("[PRINTER|DEVICE-URI]", "", "heading", False)
  1254. USAGE_ARGS2 = ("[PRINTER]", "", "heading", False)
  1255. USAGE_DEVICE = ("To specify a device-URI:", "-d<device-uri> or --device=<device-uri>", "option", False)
  1256. USAGE_PRINTER = ("To specify a CUPS printer:", "-p<printer> or --printer=<printer>", "option", False)
  1257. USAGE_BUS1 = ("Bus to probe (if device not specified):", "-b<bus> or --bus=<bus>", "option", False)
  1258. USAGE_BUS2 = ("", "<bus>: cups\*, usb\*, net, bt, fw, par\* (\*defaults) (Note: bt and fw not supported in this release.)", 'option', False)
  1259. USAGE_HELP = ("This help information:", "-h or --help", "option", True)
  1260. USAGE_SPACE = ("", "", "space", False)
  1261. USAGE_EXAMPLES = ("Examples:", "", "heading", False)
  1262. USAGE_NOTES = ("Notes:", "", "heading", False)
  1263. USAGE_STD_NOTES1 = ("If device or printer is not specified, the local device bus is probed and the program enters interactive mode.", "", "note", False)
  1264. USAGE_STD_NOTES2 = ("If -p\* is specified, the default CUPS printer will be used.", "", "note", False)
  1265. USAGE_SEEALSO = ("See Also:", "", "heading", False)
  1266. USAGE_LANGUAGE = ("Set the language:", "-q <lang> or --lang=<lang>. Use -q? or --lang=? to see a list of available language codes.", "option", False)
  1267. USAGE_LANGUAGE2 = ("Set the language:", "--lang=<lang>. Use --lang=? to see a list of available language codes.", "option", False)
  1268. USAGE_MODE = ("[MODE]", "", "header", False)
  1269. USAGE_NON_INTERACTIVE_MODE = ("Run in non-interactive mode:", "-n or --non-interactive", "option", False)
  1270. USAGE_GUI_MODE = ("Run in graphical UI mode:", "-u or --gui (Default)", "option", False)
  1271. USAGE_INTERACTIVE_MODE = ("Run in interactive mode:", "-i or --interactive", "option", False)
  1272.  
  1273. if sys_conf.get('configure', 'ui-toolkit', 'qt3') == 'qt3':
  1274.     USAGE_USE_QT3 = ("Use Qt3:",  "--qt3 (Default)",  "option",  False)
  1275.     USAGE_USE_QT4 = ("Use Qt4:",  "--qt4",  "option",  False)
  1276. else:
  1277.     USAGE_USE_QT3 = ("Use Qt3:",  "--qt3",  "option",  False)
  1278.     USAGE_USE_QT4 = ("Use Qt4:",  "--qt4 (Default)",  "option",  False)
  1279.  
  1280.  
  1281.  
  1282.  
  1283. def ttysize(): # TODO: Move to base/tui
  1284.     ln1 = commands.getoutput('stty -a').splitlines()[0]
  1285.     vals = {'rows':None, 'columns':None}
  1286.     for ph in ln1.split(';'):
  1287.         x = ph.split()
  1288.         if len(x) == 2:
  1289.             vals[x[0]] = x[1]
  1290.             vals[x[1]] = x[0]
  1291.     try:
  1292.         rows, cols = int(vals['rows']), int(vals['columns'])
  1293.     except TypeError:
  1294.         rows, cols = 25, 80
  1295.  
  1296.     return rows, cols
  1297.  
  1298.  
  1299. def usage_formatter(override=0): # TODO: Move to base/module/Module class
  1300.     rows, cols = ttysize()
  1301.  
  1302.     if override:
  1303.         col1 = override
  1304.         col2 = cols - col1 - 8
  1305.     else:
  1306.         col1 = int(cols / 3) - 8
  1307.         col2 = cols - col1 - 8
  1308.  
  1309.     return TextFormatter(({'width': col1, 'margin' : 2},
  1310.                             {'width': col2, 'margin' : 2},))
  1311.  
  1312.  
  1313. def format_text(text_list, typ='text', title='', crumb='', version=''): # TODO: Move to base/module/Module class
  1314.     """
  1315.     Format usage text in multiple formats:
  1316.         text: for --help in the console
  1317.         rest: for conversion with rst2web for the website
  1318.         man: for manpages
  1319.     """
  1320.     if typ == 'text':
  1321.         formatter = usage_formatter()
  1322.  
  1323.         for line in text_list:
  1324.             text1, text2, format, trailing_space = line
  1325.  
  1326.             # remove any reST/man escapes
  1327.             text1 = text1.replace("\\", "")
  1328.             text2 = text2.replace("\\", "")
  1329.  
  1330.             if format == 'summary':
  1331.                 log.info(log.bold(text1))
  1332.                 log.info("")
  1333.  
  1334.             elif format in ('para', 'name', 'seealso'):
  1335.                 log.info(text1)
  1336.  
  1337.                 if trailing_space:
  1338.                     log.info("")
  1339.  
  1340.             elif format in ('heading', 'header'):
  1341.                 log.info(log.bold(text1))
  1342.  
  1343.             elif format in ('option', 'example'):
  1344.                 log.info(formatter.compose((text1, text2), trailing_space))
  1345.  
  1346.             elif format == 'note':
  1347.                 if text1.startswith(' '):
  1348.                     log.info('\t' + text1.lstrip())
  1349.                 else:
  1350.                     log.info(text1)
  1351.  
  1352.             elif format == 'space':
  1353.                 log.info("")
  1354.  
  1355.         log.info("")
  1356.  
  1357.  
  1358.     elif typ == 'rest':
  1359.         opt_colwidth1, opt_colwidth2 = 0, 0
  1360.         exmpl_colwidth1, exmpl_colwidth2 = 0, 0
  1361.         note_colwidth1, note_colwidth2 = 0, 0
  1362.  
  1363.         for line in text_list:
  1364.             text1, text2, format, trailing_space = line
  1365.  
  1366.             if format  == 'option':
  1367.                 opt_colwidth1 = max(len(text1), opt_colwidth1)
  1368.                 opt_colwidth2 = max(len(text2), opt_colwidth2)
  1369.  
  1370.             elif format == 'example':
  1371.                 exmpl_colwidth1 = max(len(text1), exmpl_colwidth1)
  1372.                 exmpl_colwidth2 = max(len(text2), exmpl_colwidth2)
  1373.  
  1374.             elif format == 'note':
  1375.                 note_colwidth1 = max(len(text1), note_colwidth1)
  1376.                 note_colwidth2 = max(len(text2), note_colwidth2)
  1377.  
  1378.         opt_colwidth1 += 4
  1379.         opt_colwidth2 += 4
  1380.         exmpl_colwidth1 += 4
  1381.         exmpl_colwidth2 += 4
  1382.         note_colwidth1 += 4
  1383.         note_colwidth2 += 4
  1384.         opt_tablewidth = opt_colwidth1 + opt_colwidth2
  1385.         exmpl_tablewidth = exmpl_colwidth1 + exmpl_colwidth2
  1386.         note_tablewidth = note_colwidth1 + note_colwidth2
  1387.  
  1388.         # write the rst2web header
  1389.         log.info("""restindex
  1390. page-title: %s
  1391. crumb: %s
  1392. format: rest
  1393. file-extension: html
  1394. encoding: utf8
  1395. /restindex\n""" % (title, crumb))
  1396.  
  1397.         t = "%s: %s (ver. %s)" % (crumb, title, version)
  1398.         log.info(t)
  1399.         log.info("="*len(t))
  1400.         log.info("")
  1401.  
  1402.         links = []
  1403.         needs_header = False
  1404.         for line in text_list:
  1405.             text1, text2, format, trailing_space = line
  1406.  
  1407.             if format == 'seealso':
  1408.                 links.append(text1)
  1409.                 text1 = "`%s`_" % text1
  1410.  
  1411.             len1, len2 = len(text1), len(text2)
  1412.  
  1413.             if format == 'summary':
  1414.                 log.info(''.join(["**", text1, "**"]))
  1415.                 log.info("")
  1416.  
  1417.             elif format in ('para', 'name'):
  1418.                 log.info("")
  1419.                 log.info(text1)
  1420.                 log.info("")
  1421.  
  1422.             elif format in ('heading', 'header'):
  1423.  
  1424.                 log.info("")
  1425.                 log.info("**" + text1 + "**")
  1426.                 log.info("")
  1427.                 needs_header = True
  1428.  
  1429.             elif format == 'option':
  1430.                 if needs_header:
  1431.                     log.info(".. class:: borderless")
  1432.                     log.info("")
  1433.                     log.info(''.join(["+", "-"*opt_colwidth1, "+", "-"*opt_colwidth2, "+"]))
  1434.                     needs_header = False
  1435.  
  1436.                 if text1 and '`_' not in text1:
  1437.                     log.info(''.join(["| *", text1, '*', " "*(opt_colwidth1-len1-3), "|", text2, " "*(opt_colwidth2-len2), "|"]))
  1438.                 elif text1:
  1439.                     log.info(''.join(["|", text1, " "*(opt_colwidth1-len1), "|", text2, " "*(opt_colwidth2-len2), "|"]))
  1440.                 else:
  1441.                     log.info(''.join(["|", " "*(opt_colwidth1), "|", text2, " "*(opt_colwidth2-len2), "|"]))
  1442.  
  1443.                 log.info(''.join(["+", "-"*opt_colwidth1, "+", "-"*opt_colwidth2, "+"]))
  1444.  
  1445.             elif format == 'example':
  1446.                 if needs_header:
  1447.                     log.info(".. class:: borderless")
  1448.                     log.info("")
  1449.                     log.info(''.join(["+", "-"*exmpl_colwidth1, "+", "-"*exmpl_colwidth2, "+"]))
  1450.                     needs_header = False
  1451.  
  1452.                 if text1 and '`_' not in text1:
  1453.                     log.info(''.join(["| *", text1, '*', " "*(exmpl_colwidth1-len1-3), "|", text2, " "*(exmpl_colwidth2-len2), "|"]))
  1454.                 elif text1:
  1455.                     log.info(''.join(["|", text1, " "*(exmpl_colwidth1-len1), "|", text2, " "*(exmpl_colwidth2-len2), "|"]))
  1456.                 else:
  1457.                     log.info(''.join(["|", " "*(exmpl_colwidth1), "|", text2, " "*(exmpl_colwidth2-len2), "|"]))
  1458.  
  1459.                 log.info(''.join(["+", "-"*exmpl_colwidth1, "+", "-"*exmpl_colwidth2, "+"]))
  1460.  
  1461.             elif format == 'seealso':
  1462.                 if text1 and '`_' not in text1:
  1463.                     log.info(text1)
  1464.  
  1465.  
  1466.             elif format == 'note':
  1467.                 if needs_header:
  1468.                     log.info(".. class:: borderless")
  1469.                     log.info("")
  1470.                     log.info(''.join(["+", "-"*note_colwidth1, "+", "-"*note_colwidth2, "+"]))
  1471.                     needs_header = False
  1472.  
  1473.                 if text1.startswith(' '):
  1474.                     log.info(''.join(["|", " "*(note_tablewidth+1), "|"]))
  1475.  
  1476.                 log.info(''.join(["|", text1, " "*(note_tablewidth-len1+1), "|"]))
  1477.                 log.info(''.join(["+", "-"*note_colwidth1, "+", "-"*note_colwidth2, "+"]))
  1478.  
  1479.             elif format == 'space':
  1480.                 log.info("")
  1481.  
  1482.         for l in links:
  1483.             log.info("\n.. _`%s`: %s.html\n" % (l, l.replace('hp-', '')))
  1484.  
  1485.         log.info("")
  1486.  
  1487.     elif typ == 'man':
  1488.         log.info('.TH "%s" 1 "%s" Linux "User Manuals"' % (crumb, version))
  1489.         log.info(".SH NAME\n%s \- %s" % (crumb, title))
  1490.  
  1491.         for line in text_list:
  1492.             text1, text2, format, trailing_space = line
  1493.  
  1494.             text1 = text1.replace("\\*", "*")
  1495.             text2 = text2.replace("\\*", "*")
  1496.  
  1497.             len1, len2 = len(text1), len(text2)
  1498.  
  1499.             if format == 'summary':
  1500.                 log.info(".SH SYNOPSIS")
  1501.                 log.info(".B %s" % text1.replace('Usage:', ''))
  1502.  
  1503.             elif format == 'name':
  1504.                 log.info(".SH DESCRIPTION\n%s" % text1)
  1505.  
  1506.             elif format in ('option', 'example', 'note'):
  1507.                 if text1:
  1508.                     log.info('.IP "%s"\n%s' % (text1, text2))
  1509.                 else:
  1510.                     log.info(text2)
  1511.  
  1512.             elif format in ('header', 'heading'):
  1513.                 log.info(".SH %s" % text1.upper().replace(':', '').replace('[', '').replace(']', ''))
  1514.  
  1515.             elif format in ('seealso, para'):
  1516.                 log.info(text1)
  1517.  
  1518.         log.info(".SH AUTHOR")
  1519.         log.info("HPLIP (Hewlett-Packard Linux Imaging and Printing) is an")
  1520.         log.info("HP developed solution for printing, scanning, and faxing with")
  1521.         log.info("HP inkjet and laser based printers in Linux.")
  1522.  
  1523.         log.info(".SH REPORTING BUGS")
  1524.         log.info("The HPLIP Launchpad.net site")
  1525.         log.info(".B https://launchpad.net/hplip")
  1526.         log.info("is available to get help, report")
  1527.         log.info("bugs, make suggestions, discuss the HPLIP project or otherwise")
  1528.         log.info("contact the HPLIP Team.")
  1529.  
  1530.         log.info(".SH COPYRIGHT")
  1531.         log.info("Copyright (c) 2001-8 Hewlett-Packard Development Company, L.P.")
  1532.         log.info(".LP")
  1533.         log.info("This software comes with ABSOLUTELY NO WARRANTY.")
  1534.         log.info("This is free software, and you are welcome to distribute it")
  1535.         log.info("under certain conditions. See COPYING file for more details.")
  1536.  
  1537.         log.info("")
  1538.  
  1539.  
  1540. def log_title(program_name, version, show_ver=True): # TODO: Move to base/module/Module class
  1541.     log.info("")
  1542.  
  1543.     if show_ver:
  1544.         log.info(log.bold("HP Linux Imaging and Printing System (ver. %s)" % prop.version))
  1545.     else:
  1546.         log.info(log.bold("HP Linux Imaging and Printing System"))
  1547.  
  1548.     log.info(log.bold("%s ver. %s" % (program_name, version)))
  1549.     log.info("")
  1550.     log.info("Copyright (c) 2001-8 Hewlett-Packard Development Company, LP")
  1551.     log.info("This software comes with ABSOLUTELY NO WARRANTY.")
  1552.     log.info("This is free software, and you are welcome to distribute it")
  1553.     log.info("under certain conditions. See COPYING file for more details.")
  1554.     log.info("")
  1555.  
  1556.  
  1557. def ireplace(old, search, replace):
  1558.     regex = '(?i)' + re.escape(search)
  1559.     return re.sub(regex, replace, old)
  1560.  
  1561.  
  1562.  
  1563.  
  1564.  
  1565.